<template>
  <div class="r-draggable" :style="{ transform: transform }">
    <slot></slot>
  </div>
</template>

<script setup>
import { toValue, computed, onMounted, onUnmounted } from 'vue';
import { useState } from '@/hooks';

defineOptions({
  inheritAttrs: false,
});

const props = defineProps({
  disabled: { type: Boolean, default: true },
  nodeRef: Element,
});

const [dragging, setDragging] = useState(false);
const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 }); // 各方向可拖拽距离
const [data, setData] = useState({ x: 0, y: 0, startX: 0, startY: 0 });
const transform = computed(() => `translate(${toValue(data).x}px, ${toValue(data).y}px)`);

const onDragStart = (e) => {
  if (props.disabled) {
    return;
  }

  const targetRect = props.nodeRef?.getBoundingClientRect();
  if (!targetRect) {
    return;
  }

  const uiData = toValue(data);
  const startX = e.x - uiData.x;
  const startY = e.y - uiData.y;
  const { clientWidth, clientHeight } = window.document.documentElement;
  setData((v) => ({ ...v, startX, startY }));

  setBounds({
    left: -targetRect.left + uiData.x,
    right: clientWidth - (targetRect.right - uiData.x),
    top: -targetRect.top + uiData.y,
    bottom: clientHeight - (targetRect.bottom - uiData.y),
  });

  setDragging(true);
};

const onDragMove = (e) => {
  if (dragging.value === false) {
    return;
  }

  const { left, right, top, bottom } = toValue(bounds);
  const { startX, startY } = toValue(data);
  const moveX = e.x - startX;
  const moveY = e.y - startY;

  if (moveX >= left && moveX <= right) {
    setData((v) => ({ ...v, x: moveX }));
  }
  if (moveY >= top && moveY <= bottom) {
    setData((v) => ({ ...v, y: moveY }));
  }
};

onMounted(() => {
  addEventListener('mousedown', onDragStart);
  addEventListener('mousemove', onDragMove);
  addEventListener('mouseup', () => setDragging(false));
});
onUnmounted(() => {
  removeEventListener('mousedown', onDragStart);
  removeEventListener('mousemove', onDragMove);
  removeEventListener('mouseup', () => setDragging(false));
});
</script>

<style lang="scss" scoped></style>
